package jalgebrava.group;

import static jalgebrava.util.collections.IterableMixins.*;
import static jalgebrava.util.collections.IterableUtil.*;
import jalgebrava.util.collections.ImmutableIterable;
import jalgebrava.util.collections.ImmutableIterator;

import java.util.HashSet;
import java.util.Set;

public class Subset<G> implements HasSize, ElementsIterable<G> {
	
	private final Group<G> group; 
	private final Set<G> set;

	public Group<G> getGroup() {
		return group;
	}
	
	// same code as the iterable version below :(
	private Subset(Group<G> inGroup, G... gs) {
		
		this.group = inGroup;
		
		set = new HashSet<G>();
		for (G g: gs) {
			if (inGroup.contains(g)) {
				set.add(g);
			} else {
				throw new RuntimeException("Attempting to construct a subset with element " + g + " which is not a member of " + inGroup);
			}
		}		
	}
	
	public Subset(Group<G> inGroup, Iterable<G> gs) {
		this.group = inGroup;
		set = new HashSet<G>();
		for (G g: gs) {
			if (inGroup.contains(g)) {
				set.add(g);
			} else {
				throw new RuntimeException("Attempting to construct a subset with element " + g + " which is not a member of " + inGroup);
			}
		}		
	}

	public final static <G> Subset<G> singleton(GroupElement<G> x) {
		return new Subset<G>(x.getGroup(), singletonIterable(x.g));
	}
	
	public final int size() {
		return set.size();
	}
	@Override
	public ImmutableIterator<G> iterator() {
		return asImmutable(set).iterator();
	}
		
	@Override
	public ImmutableIterable<GroupElement<G>> elements() {
		return map(set, group.element);
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append('{');
		boolean first = true;
		for (G x: set) {
			if (!first) {
				sb.append(',');
			}
			sb.append(group.toShortString(x));
			first = false;
		}
		sb.append('}');
		sb.append('(');
		sb.append(group.getDescription());
		sb.append(')');
		return sb.toString();
	}
	
	public G pick() {
		if (set.size() > 0) {
			return set.iterator().next();
		}
		throw new RuntimeException("Can't pick from an empty set");
	}
	public boolean contains(G x) {
		for (G y: set) {
			if (group.equals(x,y)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public boolean equals(Object obj) {
		if ((obj == null) || !(obj instanceof Subset)) {
			return false;
		}
		Subset<G> other = (Subset<G>) obj;
		if (this.group != other.group) {
			return false;
		}
		if (this.set.size() != other.set.size()) {
			return false;			
		}
		// contains all will fail when the equality is different
		for (G x: other.set) {
			if (!this.contains(x)) {
				return false;
			}
		}
		return true;
	}

	@Override
	public int hashCode() {
		int result = group.hashCode();
		result *= 23017; // TODO number chosen completely at random
		for (G x: set) {			
			result += group.hashCode(x);
		}
		return result;
	}
	
	public Set<G> getCopyOfSet() {
		return new HashSet<G>(this.set);
		
	}

	public boolean isSubgroup() {
		if (!set.contains(group.unit())) {
			return false;
		}
		for (G x: this) {
			for (G y: this) {
				if (!set.contains(group.mul(x, y))) {
					return false;
				}
			}
		}
		return true;
	}

	public Subgroup<G> asSubgroup() {
		if (!isSubgroup()) {
			throw new RuntimeException("Not a subgroup");
		}
		return new Subgroup<G>(this);
	}

	public final static <G> Subset<G> mk(Group<G> g, G... gs) {
		return new Subset<G>(g,gs);
	}

}
